// HEADER FILES ------------------------------------------------------------

#define WIN32_LEAN_AND_MEAN
#define DIRECTINPUT_VERSION 0x800
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <dinput.h>

#define USE_WINDOWS_DWORD
#include "i_input.h"
#include "i_system.h"
#include "d_event.h"
#include "d_gui.h"
#include "c_cvars.h"
#include "doomdef.h"
#include "doomstat.h"
#include "win32iface.h"
#include "rawinput.h"

// MACROS ------------------------------------------------------------------

#define DINPUT_BUFFERSIZE	32

// TYPES -------------------------------------------------------------------

class FDInputKeyboard : public FKeyboard
{
public:
	FDInputKeyboard();
	~FDInputKeyboard();
	
	bool GetDevice();
	void ProcessInput();

protected:
	LPDIRECTINPUTDEVICE8 Device;
};

class FRawKeyboard : public FKeyboard
{
public:
	FRawKeyboard();
	~FRawKeyboard();

	bool GetDevice();
	bool ProcessRawInput(RAWINPUT *rawinput, int code);

protected:
	USHORT E1Prefix;
};

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

extern HWND Window;
extern LPDIRECTINPUT8 g_pdi;
extern LPDIRECTINPUT g_pdi3;
extern bool GUICapture;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

// Convert DIK_* code to ASCII using Qwerty keymap
static const BYTE Convert[256] =
{
  //  0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
	  0,  27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=',   8,   9, // 0
	'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']',  13,   0, 'a', 's', // 1
	'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',  39, '`',   0,'\\', 'z', 'x', 'c', 'v', // 2
	'b', 'n', 'm', ',', '.', '/',   0, '*',   0, ' ',   0,   0,   0,   0,   0,   0, // 3
	  0,   0,   0,   0,   0,   0,   0, '7', '8', '9', '-', '4', '5', '6', '+', '1', // 4
	'2', '3', '0', '.',   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 5
	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 6
	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // 7

	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, '=',   0,   0, // 8
	  0, '@', ':', '_',   0,   0,   0,   0,   0,   0,   0,   0,  13,   0,   0,   0, // 9
	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // A
	  0,   0,   0, ',',   0, '/',   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // B
	  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, // C
	  0,   0,   0,   0,   0,   0,   0,   0

};

// PUBLIC DATA DEFINITIONS -------------------------------------------------

FKeyboard *Keyboard;

// Set this to false to make keypad-enter a usable separate key.
CVAR (Bool, k_mergekeys, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)

// CODE --------------------------------------------------------------------

//==========================================================================
//
// FKeyboard - Constructor
//
//==========================================================================

FKeyboard::FKeyboard()
{
	memset(KeyStates, 0, sizeof(KeyStates));
}

//==========================================================================
//
// FKeyboard - Destructor
//
//==========================================================================

FKeyboard::~FKeyboard()
{
	AllKeysUp();
}

//==========================================================================
//
// FKeyboard :: CheckAndSetKey
//
// Returns true if the key was already in the desired state, false if it
// wasn't.
//
//==========================================================================

bool FKeyboard::CheckAndSetKey(int keynum, INTBOOL down)
{
	BYTE *statebyte = &KeyStates[keynum >> 3];
	BYTE mask = 1 << (keynum & 7);
	if (down)
	{
		if (*statebyte & mask)
		{
			return true;
		}
		*statebyte |= mask;
		return false;
	}
	else
	{
		if (*statebyte & mask)
		{
			*statebyte &= ~mask;
			return false;
		}
		return true;
	}
}

//==========================================================================
//
// FKeyboard :: AllKeysUp
//
// For every key currently marked as down, send a key up event and clear it.
//
//==========================================================================

void FKeyboard::AllKeysUp()
{
	event_t ev = { 0 };
	ev.type = EV_KeyUp;

	for (int i = 0; i < 256/8; ++i)
	{
		if (KeyStates[i] != 0)
		{
			BYTE states = KeyStates[i];
			int j = 0;
			KeyStates[i] = 0;
			do
			{
				if (states & 1)
				{
					ev.data1 = (i << 3) + j;
					ev.data2 = Convert[ev.data1];
					D_PostEvent(&ev);
				}
				states >>= 1;
				++j;
			}
			while (states != 0);
		}
	}
}

//==========================================================================
//
// FKeyboard :: PostKeyEvent
//
// Posts a keyboard event, but only if the state is different from what we
// currently think it is. (For instance, raw keyboard input sends key
// down events every time the key automatically repeats, so we want to
// discard those.)
//
//==========================================================================

void FKeyboard::PostKeyEvent(int key, INTBOOL down, bool foreground)
{
	event_t ev = { 0 };

//	Printf("key=%02x down=%02x\n", key, down);
	// "Merge" multiple keys that are considered to be the same. If the
	// original unmerged key is down, it also needs to go up. (In case
	// somebody was holding the key down when they changed this setting.)
	if (k_mergekeys)
	{
		if (key == DIK_NUMPADENTER || key == DIK_RMENU || key == DIK_RCONTROL)
		{
			k_mergekeys = false;
			PostKeyEvent(key, false, foreground);
			k_mergekeys = true;
			key &= 0x7F;
		}
		else if (key == DIK_RSHIFT)
		{
			k_mergekeys = false;
			PostKeyEvent(key, false, foreground);
			k_mergekeys = true;
			key = DIK_LSHIFT;
		}
	}
	if (key == 0x59)
	{ // Turn kp= on a Mac keyboard into kp= on a PC98 keyboard.
		key = DIK_NUMPADEQUALS;
	}

	// Generate the event, if appropriate.
	if (down)
	{
		if (!foreground || GUICapture)
		{ // Do not generate key down events if we are in the background
		  // or in "GUI Capture" mode.
			return;
		}
		ev.type = EV_KeyDown;
	}
	else
	{
		ev.type = EV_KeyUp;
	}
	if (CheckAndSetKey(key, down))
	{ // Key is already down or up.
		return;
	}
	ev.data1 = key;
	ev.data2 = Convert[key];
	D_PostEvent(&ev);
}

//==========================================================================
//
// FDInputKeyboard - Constructor
//
//==========================================================================

FDInputKeyboard::FDInputKeyboard()
{
	Device = NULL;
}

//==========================================================================
//
// FDInputKeyboard - Destructor
//
//==========================================================================

FDInputKeyboard::~FDInputKeyboard()
{
	if (Device != NULL)
	{
		Device->Release();
		Device = NULL;
	}
}

//==========================================================================
//
// FDInputKeyboard :: GetDevice
//
// Create the device interface and initialize it.
//
//==========================================================================

bool FDInputKeyboard::GetDevice()
{
	HRESULT hr;

	if (g_pdi3 != NULL)
	{ // DirectInput3 interface
		hr = g_pdi3->CreateDevice(GUID_SysKeyboard, (LPDIRECTINPUTDEVICE*)&Device, NULL);
	}
	else if (g_pdi != NULL)
	{ // DirectInput8 interface
		hr = g_pdi->CreateDevice(GUID_SysKeyboard, &Device, NULL);
	}
	else
	{
		hr = -1;
	}
	if (FAILED(hr))
	{
		return false;
	}

	// Yes, this is a keyboard.
	hr = Device->SetDataFormat(&c_dfDIKeyboard);
	if (FAILED(hr))
	{
ufailit:
		Device->Release();
		Device = NULL;
		return false;
	}
	// Set cooperative level.
	hr = Device->SetCooperativeLevel(Window, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
	if (FAILED(hr))
	{
		goto ufailit;
	}
	// Set buffer size so we can use buffered input.
	DIPROPDWORD prop;
	prop.diph.dwSize = sizeof(prop);
	prop.diph.dwHeaderSize = sizeof(prop.diph);
	prop.diph.dwObj = 0;
	prop.diph.dwHow = DIPH_DEVICE;
	prop.dwData = DINPUT_BUFFERSIZE;
	hr = Device->SetProperty(DIPROP_BUFFERSIZE, &prop.diph);
	if (FAILED(hr))
	{
		goto ufailit;
	}
	Device->Acquire();
	return true;
}

//==========================================================================
//
// FDInputKeyboard :: ProcessInput
//
//==========================================================================

void FDInputKeyboard::ProcessInput()
{
	DIDEVICEOBJECTDATA od;
	DWORD dwElements;
	HRESULT hr;
	bool foreground = (GetForegroundWindow() == Window);

	for (;;)
	{
		DWORD cbObjectData = g_pdi3 ? sizeof(DIDEVICEOBJECTDATA_DX3) : sizeof(DIDEVICEOBJECTDATA);
		dwElements = 1;
		hr = Device->GetDeviceData(cbObjectData, &od, &dwElements, 0);
		if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
		{
			Device->Acquire();
			hr = Device->GetDeviceData(cbObjectData, &od, &dwElements, 0);
		}
		if (FAILED(hr) || !dwElements)
		{
			break;
		}

		if (od.dwOfs >= 1 && od.dwOfs <= 255)
		{
			PostKeyEvent(od.dwOfs, od.dwData & 0x80, foreground);
		}
	}
}

/**************************************************************************/
/**************************************************************************/

//==========================================================================
//
// FRawKeyboard - Constructor
//
//==========================================================================

FRawKeyboard::FRawKeyboard()
{
	E1Prefix = 0;
}

//==========================================================================
//
// FRawKeyboard - Destructor
//
//==========================================================================

FRawKeyboard::~FRawKeyboard()
{
	if (MyRegisterRawInputDevices != NULL)
	{
		RAWINPUTDEVICE rid;
		rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE;
		rid.usUsage = HID_GDP_KEYBOARD;
		rid.dwFlags = RIDEV_REMOVE;
		rid.hwndTarget = NULL;
		MyRegisterRawInputDevices(&rid, 1, sizeof(rid));
	}
}

//==========================================================================
//
// FRawKeyboard :: GetDevice
//
// Ensure the API is present and we can listen for keyboard input.
//
//==========================================================================

bool FRawKeyboard::GetDevice()
{
	RAWINPUTDEVICE rid;

	if (MyRegisterRawInputDevices == NULL)
	{
		return false;
	}
	rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE;
	rid.usUsage = HID_GDP_KEYBOARD;
	rid.dwFlags = RIDEV_INPUTSINK;
	rid.hwndTarget = Window;
	if (!MyRegisterRawInputDevices(&rid, 1, sizeof(rid)))
	{
		return false;
	}
	return true;
}

//==========================================================================
//
// FRawKeyboard :: ProcessRawInput
//
// Convert scan codes to DirectInput key codes. For the most part, this is
// straight forward: Scan codes without any prefix are passed unmodified.
// Scan codes with an 0xE0 prefix byte are generally passed by ORing them
// with 0x80. And scan codes with an 0xE1 prefix are the annoying Pause key
// which will generate another scan code that looks like Num Lock.
//
// This is a bit complicated only because the state of PC key codes is a bit
// of a mess. Keyboards may use simpler codes internally, but for the sake
// of compatibility, programs are presented with XT-compatible codes. This
// means that keys which were originally a shifted form of another key and
// were split off into a separate key all their own, or which were formerly
// a separate key and are now part of another key (most notable PrtScn and
// SysRq), will still generate code sequences that XT-era software will
// still perceive as the original sequences to use those keys.
//
//==========================================================================

bool FRawKeyboard::ProcessRawInput(RAWINPUT *raw, int code)
{
	if (raw->header.dwType != RIM_TYPEKEYBOARD)
	{
		return false;
	}
	int keycode = raw->data.keyboard.MakeCode;
	if (keycode == 0 && (raw->data.keyboard.Flags & RI_KEY_E0))
	{ // Even if the make code is 0, we might still be able to extract a
	  // useful key from the message.
		if (raw->data.keyboard.VKey >= VK_BROWSER_BACK && raw->data.keyboard.VKey <= VK_LAUNCH_APP2)
		{
			static const BYTE MediaKeys[VK_LAUNCH_APP2 - VK_BROWSER_BACK + 1] =
			{
				DIK_WEBBACK, DIK_WEBFORWARD, DIK_WEBREFRESH, DIK_WEBSTOP,
				DIK_WEBSEARCH, DIK_WEBFAVORITES, DIK_WEBHOME,

				DIK_MUTE, DIK_VOLUMEDOWN, DIK_VOLUMEUP,
				DIK_NEXTTRACK, DIK_PREVTRACK, DIK_MEDIASTOP, DIK_PLAYPAUSE,

				DIK_MAIL, DIK_MEDIASELECT, DIK_MYCOMPUTER, DIK_CALCULATOR
			};
			keycode = MediaKeys[raw->data.keyboard.VKey - VK_BROWSER_BACK];
		}
	}
	if (keycode < 1 || keycode > 0xFF)
	{
		return false;
	}
	if (raw->data.keyboard.Flags & RI_KEY_E1)
	{
		E1Prefix = raw->data.keyboard.MakeCode;
		return false;
	}
	if (raw->data.keyboard.Flags & RI_KEY_E0)
	{
		if (keycode == DIK_LSHIFT || keycode == DIK_RSHIFT)
		{ // Ignore fake shifts.
			return false;
		}
		keycode |= 0x80;
	}
	// The sequence for an unshifted pause is E1 1D 45 (E1 Prefix +
	// Control + Num Lock).
	if (E1Prefix)
	{
		if (E1Prefix == 0x1D && keycode == DIK_NUMLOCK)
		{
			keycode = DIK_PAUSE;
			E1Prefix = 0;
		}
		else
		{
			E1Prefix = 0;
			return false;
		}
	}
	// If you press Ctrl+Pause, the keyboard sends the Break make code
	// E0 46 instead of the Pause make code.
	if (keycode == 0xC6)
	{
		keycode = DIK_PAUSE;
	}
	// If you press Ctrl+PrtScn (to get SysRq), the keyboard sends
	// the make code E0 37. If you press PrtScn without any modifiers,
	// it sends E0 2A E0 37. And if you press Alt+PrtScn, it sends 54
	// (which is undefined in the charts I can find.)
	if (keycode == 0x54)
	{
		keycode = DIK_SYSRQ;
	}
	// If you press any keys in the island between the main keyboard
	// and the numeric keypad with Num Lock turned on, they generate
	// a fake shift before their actual codes. They do not generate this
	// fake shift if Num Lock is off. We unconditionally discard fake
	// shifts above, so we don't need to do anything special for these,
	// since they are also prefixed by E0 so we can tell them apart from
	// their keypad counterparts.

	// Okay, we're done translating the keycode. Post it (or ignore it.)
	PostKeyEvent(keycode, !(raw->data.keyboard.Flags & RI_KEY_BREAK), code == RIM_INPUT);
	return true;
}

//==========================================================================
//
// I_StartupKeyboard
//
//==========================================================================

void I_StartupKeyboard()
{
	Keyboard = new FRawKeyboard;
	if (Keyboard->GetDevice())
	{
		return;
	}
	delete Keyboard;
	Keyboard = new FDInputKeyboard;
	if (!Keyboard->GetDevice())
	{
		delete Keyboard;
	}
}

